﻿#region Using
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion Using

namespace LloydGame
{
    public abstract class ShadowMap : GameComponent
    {
        #region Class Fields
        protected int m_shadowMapSize;
        protected Matrix m_viewProj;

        protected DepthStencilBuffer m_shadowStencilBuffer;
        protected Texture m_staticShadowMap;
        protected Texture m_shadowMap;

        protected Effect m_depthMapEffect;
        #endregion Class Fields

        #region Properties
        public Texture shadowMap
        {
            get
            {
                return m_shadowMap;
            }
        }

        public Matrix viewProj
        {
            get
            {
                return m_viewProj;
            }
        }
        #endregion Properties

        // constructor
        public ShadowMap(Game p_game, int p_shadowMapSize) : base(p_game)
        {
            m_shadowMapSize = p_shadowMapSize;

            m_depthMapEffect = Game.Content.Load<Effect>("Effects\\DepthMap");

            m_depthMapEffect.CurrentTechnique = m_depthMapEffect.Techniques["DepthMap"];
        }

        #region Shadow Map Creation
        public abstract void createShadowMap(GameTime p_gameTime, GameLight p_light, bool p_createStatic);


        protected void drawDepthMap(GameTime p_gameTime, bool p_createStatic)
        {
            BoundingFrustum viewFrustum = new BoundingFrustum(m_viewProj);

            // get the cameras view frustum
            float cameraFarClipBackup = ((Game1)Game).camera.farClip;
            ((Game1)Game).camera.farClip = 200.0f;

            BoundingFrustum cameraViewFrustum = new BoundingFrustum(((Game1)Game).camera.viewProj);

            ((Game1)Game).camera.farClip = cameraFarClipBackup;

            // do not render dynamic depth maps if it won't be used in the scene
            if (viewFrustum.Contains(cameraViewFrustum) == ContainmentType.Disjoint)
            {
                return;
            }

            foreach (GameObject gameObject in ((Game1)Game).gameWorld.gameObjects)
            {
                // render only static/dynamic objects to static/dynamic shadow map
                if (!p_createStatic && gameObject.staticFlag || p_createStatic && !gameObject.staticFlag)
                {
                    continue;
                }

                foreach (GameModel gameModel in gameObject.gameModels)
                {
                    BoundingSphere modelBound = new BoundingSphere(gameObject.position + gameModel.position, 1);

                    // calculate the entire models bounding sphere
                    foreach (ModelMesh mesh in gameModel.model.Meshes)
                    {
                        BoundingSphere meshBound = mesh.BoundingSphere;

                        meshBound.Center = gameObject.position + gameModel.position;

                        modelBound = BoundingSphere.CreateMerged(modelBound, meshBound);
                    }

                    modelBound.Radius *=
                        MathHelper.Max(MathHelper.Max(gameObject.scale.X, gameObject.scale.Y), gameObject.scale.Z) *
                        MathHelper.Max(MathHelper.Max(gameModel.scale.X, gameModel.scale.Y), gameModel.scale.Z);

                    // cull away if the entire model is outside the view frustum
                    if (viewFrustum.Contains(modelBound) == ContainmentType.Disjoint)
                    {
                        continue;
                    }

                    Matrix world =
                        Matrix.CreateScale(gameObject.scale * gameModel.scale) *
                        Matrix.CreateFromYawPitchRoll(
                            gameObject.rotation.Y + gameModel.rotation.Y,
                            gameObject.rotation.X + gameModel.rotation.X,
                            gameObject.rotation.Z + gameModel.rotation.Z) *
                        Matrix.CreateTranslation(gameObject.position + gameModel.position);

                    m_depthMapEffect.Parameters["world"].SetValue(world);
                    m_depthMapEffect.Parameters["wvp"].SetValue(world * m_viewProj);

                    foreach (ModelMesh mesh in gameModel.model.Meshes)
                    {
                        BoundingSphere meshBound = mesh.BoundingSphere;

                        // calculate the mesh's bounding sphere
                        meshBound.Center = gameObject.position + gameModel.position;
                        meshBound.Radius *=
                            MathHelper.Max(MathHelper.Max(gameObject.scale.X, gameObject.scale.Y), gameObject.scale.Z) *
                            MathHelper.Max(MathHelper.Max(gameModel.scale.X, gameModel.scale.Y), gameModel.scale.Z);

                        // cull away if the mesh is outside the view frustum
                        if (viewFrustum.Contains(meshBound) == ContainmentType.Disjoint)
                        {
                            continue;
                        }

                        m_depthMapEffect.Begin();
                        m_depthMapEffect.CurrentTechnique.Passes["DrawDepth"].Begin();

                        foreach (ModelMeshPart meshPart in mesh.MeshParts)
                        {
                            gameObject.drawMeshPart(mesh, meshPart);
                        }

                        m_depthMapEffect.CurrentTechnique.Passes["DrawDepth"].End();
                        m_depthMapEffect.End();
                    }
                }
            }
        }
        #endregion Shadow Map Creation
    }
}
